/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2023.
 * Description: reserved block for bad block
 * Author: Pang LiYuan
 * Create: 2023-09-13
 */
#ifdef CONFIG_RTOS_STORAGE_UBI_BEB_RSVD
#include <linux/moduleparam.h>

/* UBI PEBS reserved for bad eraseblock set minimum */
#define UBI_BEB_RSVD_MIN 4

/* UBI PEBS reserved for bad eraseblock calculate num */
#define UBI_BEB_RSVD_CAL_NUM 2

/* UBI PEBS reserved for bad eraseblock divide num  */
#define UBI_BEB_RSVD_DIV_NUM 100

/* UBI calculate PEBS for bad eraseblock algorithm enable */
static bool beb_rsvd;
module_param(beb_rsvd, bool, 0640);
MODULE_PARM_DESC(beb_rsvd, "support customize the beb_rsvd");

/* PEBs reserved for bad eraseblock handling, depend on beb_rsvd enable */
static int beb_rsvd_percent = -1;
module_param(beb_rsvd_percent, int, 0640);
MODULE_PARM_DESC(beb_rsvd_percent, "the percent of reserved PEBs for bad eraseblock handling");

void ubi_calculate_reserved(struct ubi_device *ubi)
{
	/*
	 * Calculate the actual number of PEBs currently needed to be reserved
	 * for future bad eraseblock handling.
	 */

	unsigned long long tmp_beb_rsvd_level;

	if (!beb_rsvd) {
		ubi->beb_rsvd_level = ubi->bad_peb_limit - ubi->bad_peb_count;
		if (ubi->beb_rsvd_level < 0) {
			ubi->beb_rsvd_level = 0;
			ubi_warn(ubi, "%d bad PEBs beyond limit %d, not reserving any PEBs, use available PEBs (if any)",
				ubi->bad_peb_count, ubi->bad_peb_limit);
		}
	} else {
		if (beb_rsvd_percent < 0 || beb_rsvd_percent >= 100) {
			ubi->beb_rsvd_level = ubi->good_peb_count /
						UBI_BEB_RSVD_DIV_NUM;
			ubi->beb_rsvd_level *= UBI_BEB_RSVD_CAL_NUM;
			if (ubi->beb_rsvd_level < UBI_BEB_RSVD_MIN)
				ubi->beb_rsvd_level = UBI_BEB_RSVD_MIN;
			ubi_msg(ubi, "beb_rsvd_percent not set or invalid, will use default value");
		} else {
			tmp_beb_rsvd_level = (unsigned long long)ubi->good_peb_count *
						beb_rsvd_percent;
			tmp_beb_rsvd_level = div64_u64(tmp_beb_rsvd_level, UBI_BEB_RSVD_DIV_NUM);
			/* Actually tmp_beb_rsvd_level <= ubi->good_peb_count < INT_MAX */
			if (tmp_beb_rsvd_level > 0)
				ubi->beb_rsvd_level = (int)tmp_beb_rsvd_level;
			else
				ubi->beb_rsvd_level = UBI_BEB_RSVD_MIN;
		}
	}
}
#endif